﻿using BeetleX.Buffers;
using BeetleX.Clients;
using BeetleX.Dispatchs;
using BeetleX.XRPC.Awaiter;
using BeetleX.XRPC.Packets;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Security;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

#nullable disable
namespace BeetleX.XRPC.Clients
{
    public class XRPCClient : IDisposable
    {
        private List<AsyncTcpClient> mClients = new List<AsyncTcpClient>();
        private RemoteCertificateValidationCallback mCertificateValidationCallback;
        private long mRequests;
        private long mResponses;
        private long mIndex;
        private string mSslServerName;
        private DispatchCenter<RPCPacket> mReceiveDispatchCenter;
        private AwaiterFactory mAwaiterFactory;
        private Timer mPingTimer;
        private int mPingTime;
        public const string DELEGATE_TAG = "/__delegate/";
        public const string SUBSCRIBE_TAG = "/__subscribe/";
        public const string LOGIN_TAG = "/__login/";
        private ConcurrentDictionary<string, DelegateHandler> mDelegateHandlers = new ConcurrentDictionary<string, DelegateHandler>((IEqualityComparer<string>)StringComparer.OrdinalIgnoreCase);
        private ConcurrentDictionary<string, DelegateHandler> mServerDelegates = new ConcurrentDictionary<string, DelegateHandler>((IEqualityComparer<string>)StringComparer.OrdinalIgnoreCase);

        public EventClientError NetError { get; set; }

        static XRPCClient()
        {
            if (BufferPool.BUFFER_SIZE >= 16384)
                return;
            BufferPool.BUFFER_SIZE = 16384;
        }

        public XRPCClient(string host, int port, string sslServiceName, int maxConnections = 1)
        {
            this.mSslServerName = sslServiceName ?? this.Host;
            this.Host = host;
            this.Port = port;
            this.MaxConnections = maxConnections;
            this.Init();
        }

        public XRPCClient(string host, int port, int maxConnections = 1)
        {
            this.Host = host;
            this.Port = port;
            this.MaxConnections = maxConnections;
            this.Init();
        }

        private void Init()
        {
            this.mReceiveDispatchCenter = new DispatchCenter<RPCPacket>(new Action<RPCPacket>(this.OnProcess));
            this.mAwaiterFactory = new AwaiterFactory(false);
            this.InitConnect();
            this.mPingTimer = new Timer(new TimerCallback(this.OnPing), (object)null, 10000, 10000);
        }

        public ControllerCenter Controllers { get; private set; } = new ControllerCenter();

        public List<AsyncTcpClient> Clients => this.mClients;

        public RemoteCertificateValidationCallback CertificateValidationCallback
        {
            get => this.mCertificateValidationCallback;
            set
            {
                this.mCertificateValidationCallback = value;
                foreach (var mClient in this.mClients)
                    mClient.CertificateValidationCallback = this.mCertificateValidationCallback;
            }
        }

        public int MaxConnections { get; private set; }

        public Options Options { get; set; } = new Options();

        public AwaiterFactory AwaiterFactory => this.mAwaiterFactory;

        public EventHandler<RPCPacket> Receive { get; set; }

        private async void InvokeController(ControllerCenter.HandlerItem handler, RPCPacket packet)
        {
            using RPCPacket response = new RPCPacket();
            try
            {
                packet.LoadParameters(handler.Parameters);
                object result = handler.Handler.Execute(handler.Controller, packet.Data);
                if (result is Task task)
                    await task;
                object obj = handler.GetValue(result);
                if (obj != null)
                    response.Data = new object[1] { obj };
                result = null;
            }
            catch (Exception ex)
            {
                response.Status = 500;
                response.Data = new string[1] { ex.Message };
            }
            packet.ReplyPacket(response);
        }

        protected virtual void OnProcess(RPCPacket response)
        {
            AwaiterItem awaiterItem = this.mAwaiterFactory.GetItem(response.ID);
            if (awaiterItem != null)
            {
                response.ResultType = awaiterItem.ResultType;
                try
                {
                    response.LoadParameters(response.ResultType);
                }
                catch (Exception ex)
                {
                    response.Status = (short)500;
                    response.Data = new object[1] { (ex.Message + "@" + ex.StackTrace) };
                }
                this.mAwaiterFactory.Completed(awaiterItem, response);
            }
            else
            {
                try
                {
                    if (response.Url.IndexOf("/__delegate/", StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        this.InvokeDelegate(response);
                    }
                    else
                    {
                        ControllerCenter.HandlerItem handler = this.Controllers.GetHandler(response.Url);
                        if (handler != null)
                            this.InvokeController(handler, response);
                        else if (this.Receive != null)
                        {
                            this.Receive(this, response);
                        }
                        else
                        {
                            if (!response.NeedReply)
                                return;
                            response.ReplyPacket(new RPCPacket()
                            {
                                Status = 403,
                                Data = new object[1] { (response.Url + " not found!") }
                            });
                        }
                    }
                }
                catch (Exception ex)
                {
                    this.OnError((IClient)response.Client, new ClientErrorArgs()
                    {
                        Error = ex,
                        Message = "Packet process event error " + ex.Message
                    });
                }
            }
        }

        private void OnError(IClient client, ClientErrorArgs e)
        {
            try { NetError?.Invoke(client, e); }
            catch { }
        }

        private void OnPacketCompleted(IClient client, object message)
        {
            RPCPacket data = (RPCPacket)message;
            Interlocked.Increment(ref this.mResponses);
            this.mReceiveDispatchCenter.Enqueue(data);
        }

        private void InitConnect()
        {
            for (int index = 0; index < this.MaxConnections; ++index)
            {
                ClientPacket packet = new ClientPacket();
                packet.Options = this.Options;
                AsyncTcpClient client = this.mSslServerName != null ? SocketFactory.CreateSslClient<AsyncTcpClient>((IClientPacket)packet, this.Host, this.Port, this.mSslServerName) : SocketFactory.CreateClient<AsyncTcpClient>((IClientPacket)packet, this.Host, this.Port);
                client.PacketReceive = new EventClientPacketCompleted(this.OnPacketCompleted);
                client.ClientError = new EventClientError(this.OnError);
                this.mClients.Add(client);
            }
        }

        private AsyncTcpClient GetClient()
        {
            return this.mClients[(int)(Interlocked.Increment(ref this.mIndex) % (long)this.mClients.Count)];
        }

        public long Requests => this.mRequests;

        public long Responses => this.mResponses;

        public string Host { get; set; }

        public int Port { get; set; }

        public int TimeOut { get; set; } = 10000;

        public T Create<T>(string actorID = null)
        {
            // ISSUE: variable of a boxed type
            object local;
            XRPCClientDispatch xrpcClientDispatch = (XRPCClientDispatch)(local = (object)DispatchProxy.Create<T, XRPCClientDispatch>());
            xrpcClientDispatch.Client = this;
            xrpcClientDispatch.Actor = actorID;
            xrpcClientDispatch.Type = typeof(T);
            xrpcClientDispatch.InitHandlers();
            return (T)local;
        }

        public void Register<Service>(Service serviceImpl)
        {
            this.Controllers.Register<Service>(serviceImpl);
        }

        public async Task Send(RPCPacket request, AsyncTcpClient client = null)
        {
            client = client ?? this.GetClient();
            ConnectStatus connectStatus = await client.Connect();
            if (!connectStatus.Connected)
                throw client.LastError;
            if (connectStatus.NewConnection)
                await this.OnLogin(client);
            AsyncTcpClient asyncTcpClient = await client.Send((object)request);
            Interlocked.Increment(ref this.mRequests);
        }

        public async Task<RPCPacket> SendWait(
          RPCPacket request,
          AsyncTcpClient client,
          Type[] resultType = null)
        {
            client = client ?? this.GetClient();
            ConnectStatus connectStatus = await client.Connect();
            if (!connectStatus.Connected)
                throw client.LastError;
            if (connectStatus.NewConnection)
                await this.OnLogin(client);
            (int, TaskCompletionSource<RPCPacket>) result = this.mAwaiterFactory.Create(request, resultType, this.TimeOut);
            request.ID = result.Item1;
            AsyncTcpClient asyncTcpClient = await client.Send((object)request);
            Interlocked.Increment(ref this.mRequests);
            return await result.Item2.Task;
        }

        public int PingTime
        {
            get => this.mPingTime;
            set
            {
                this.mPingTime = value;
                if (this.mPingTime < 1000)
                    this.mPingTime = 1000;
                if (this.mPingTime > 0)
                    this.mPingTimer.Change(this.mPingTime, this.mPingTime);
                else
                    this.mPingTimer.Change(30000, 30000);
            }
        }

        public void Ping() => this.OnPing((object)null);

        private void OnPing(object state)
        {
            foreach (AsyncTcpClient client in this.Clients)
                Ping(client);
        }

        private async void Ping(AsyncTcpClient client)
        {
            try
            {
                RPCPacket rpcPacket = await this.SendWait(new RPCPacket()
                {
                    Url = "/__System/Ping"
                }, client);
            }
            catch (Exception ex)
            {
                client.DisConnect();
            }
        }

        public string UserName { get; set; }

        public string PassWord { get; set; }

        protected virtual async Task OnLogin(AsyncTcpClient client)
        {
            if (!string.IsNullOrEmpty(this.UserName))
            {
                RPCPacket rpcPacket = await this.SendWait(new RPCPacket()
                {
                    Url = "/__login/",
                    Data = new object[2]
                  {
            (object) this.UserName,
            (object) this.PassWord
                  },
                    NeedReply = true
                }, client);
                if (rpcPacket.Status != (short)200)
                {
                    client.DisConnect();
                    throw new XRPCException((string)rpcPacket.Data[0]);
                }
            }
            if (this.Connected == null)
                return;
            await this.Connected(client);
        }

        public Func<AsyncTcpClient, Task> Connected { get; set; }

        private async void InvokeDelegate(RPCPacket packet)
        {
            XRPCClient xrpcClient = this;
            string action;
            packet.Url.SubRightWith('/', out action);
            DelegateHandler handler;
            if (xrpcClient.mDelegateHandlers.TryGetValue(action, out handler))
            {
                try
                {
                    packet.LoadParameters(handler.Parameters);
                    object result = handler.Delegate.DynamicInvoke(packet.Data);
                    if (!handler.IsVoid)
                    {
                        await (Task)result;
                        if (handler.TargetReturnType != (Type)null)
                        {
                            object obj = handler.GetValue(result);
                            packet.Reply(obj);
                        }
                        else
                            packet.ReplySuccess();
                    }
                    result = (object)null;
                    action = (string)null;
                    handler = (DelegateHandler)null;
                }
                catch (Exception ex)
                {
                    if (!handler.IsVoid)
                    {
                        packet.ReplyError((short)500, action + " delegate invoke error " + ex.Message + "!");
                        action = (string)null;
                        handler = (DelegateHandler)null;
                    }
                    else
                    {
                        xrpcClient.OnError((IClient)packet.Client, new ClientErrorArgs()
                        {
                            Error = ex,
                            Message = action + " delegate invoke error " + ex.Message
                        });
                        action = (string)null;
                        handler = (DelegateHandler)null;
                    }
                }
            }
            else if (!packet.NeedReply)
            {
                action = (string)null;
                handler = (DelegateHandler)null;
            }
            else
            {
                packet.ReplyError((short)404, action + " delegate not found!");
                action = (string)null;
                handler = (DelegateHandler)null;
            }
        }

        public XRPCClient AddDelegate<T>(T handler) where T : System.Delegate
        {
            DelegateHandler delegateHandler = new DelegateHandler(typeof(T));
            delegateHandler.Delegate = (System.Delegate)handler;
            delegateHandler.Init();
            this.mDelegateHandlers[delegateHandler.Name] = delegateHandler;
            return this;
        }

        public T Delegate<T>() where T : System.Delegate
        {
            DelegateHandler delegateHandler;
            if (!this.mServerDelegates.TryGetValue(DelegateHandler.GetDelegateName(typeof(T)), out delegateHandler))
            {
                delegateHandler = new DelegateHandler(typeof(T));
                delegateHandler.Init();
                delegateHandler.Bind(this);
                this.mServerDelegates[delegateHandler.Name] = delegateHandler;
            }
            return (T)delegateHandler.ClientDelegateProxy;
        }

        public async Task Subscribe<T>(T handler, AsyncTcpClient client = null) where T : System.Delegate
        {
            DelegateHandler item = new DelegateHandler(typeof(T));
            item.Delegate = (System.Delegate)handler;
            item.Init();
            if (!item.IsVoid)
                throw new XRPCException("The subscribe delegate return value must be void!");
            if (item.Parameters == null || item.Parameters.Length == 0)
                throw new XRPCException("The subscribe delegate parameters can't be empty!");
            RPCPacket rpcPacket = await this.SendWait(new RPCPacket()
            {
                Url = "/__subscribe/" + item.Name,
                NeedReply = true
            }, client);
            this.mDelegateHandlers[item.Name] = item;
            item = (DelegateHandler)null;
        }

        #region Dispose
        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    mPingTimer?.Dispose();
                    mPingTimer = null;

                    if (mClients.Count > 0)
                    {
                        foreach (var item in mClients)
                            item?.Dispose();

                        mClients.Clear();
                    }
                }
                // TODO: 释放未托管的资源(未托管的对象)并重写终结器
                // TODO: 将大型字段设置为 null
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}